 /**
 * 文件名称: framebuffer.c
 * 摘    要: linux framebuffer基本操作源文件
 * 来    源: 参考朱有鹏老师嵌入式linux核心课程中《图片解码播放器》编写的图片播放器工程
 *
 * 当前版本: 2.0 
 * 作    者: huenrong
 * 完成日期: 2019-07-20
 **/

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <math.h>

#include "framebuffer.h"

unsigned int *pfb = NULL;       // pfb指向framebuffer内存地址首地址(要显示数据往该起始地址开始赋值数据即可)

/******************************************************************************
函数名称: open_framebuffer
函数功能: 打开framebuffer
函数参数: 无
函数返回: 成功: 返回打开framebuffer的文件描述符
          失败: 返回-1
******************************************************************************/
int open_framebuffer(void)
{
    int ret = -1;
    int framebuffer_fd = -1;        // 打开打开framebuffer的文件描述符
    // 和framebuffer相关的两个结构体
    struct fb_fix_screeninfo fix_info = {{0}};      // 记录用户不可修改的显示控制器的参数
    struct fb_var_screeninfo var_info = {0};        // 记录用户可修改的显示控制器的参数

    framebuffer_fd = open(FB_DEVICE, O_RDWR);       // 打开设备
    if (framebuffer_fd < 0)
    {
        perror("open_framebuffer open error");

        return -1;
    }

    ret = ioctl(framebuffer_fd, FBIOGET_FSCREENINFO, &fix_info);       // 获取设备不可修改的硬件信息
    if (ret < 0)
    {
        perror("open_framebuffer ioctl fix_info error");

        return -1;
    }
    // smem_start: fb缓冲内存的开始地址(物理地址)
    // smem_len: fb缓冲的长度, 等于max_xres * max_yres * max_bpp / 8
    // printf("smem_start = 0x%lx, smem_len = %u\r\n", fix_info.smem_start, fix_info.smem_len);

    ret = ioctl(framebuffer_fd, FBIOGET_VSCREENINFO, &var_info);       // 获取设备可修改的硬件信息
    if (ret < 0)
    {
        perror("open_framebuffer ioctl var_info error");

        return -1;
    }
    // xres: 屏幕一行有多少个像素点(实际屏幕)
    // yres: 屏幕一列有多少个像素点(实际屏幕)
    // xres_virtual: 屏幕一行有多少个像素点(虚拟屏幕)
    // yres_virtual: 屏幕一列有多少个像素点(虚拟屏幕)
    // bits_per_pixel:  每像素位数(多少bpp), 单位为字节
    printf("------------------screen info------------------\r\n");
    printf("xres = %u, yres = %u.\n", var_info.xres, var_info.yres);
    printf("xres_virtual = %u, yres_virtual = %u.\n", var_info.xres_virtual, var_info.yres_virtual);
    printf("bpp = %u.\n", var_info.bits_per_pixel);
    printf("-----------------------------------------------\r\n");

    unsigned long len = var_info.xres_virtual * var_info.yres_virtual * var_info.bits_per_pixel / 8;
    // printf("len = %ld\n", len);

    pfb = mmap(NULL, len, (PROT_READ | PROT_WRITE), MAP_SHARED, framebuffer_fd, 0);
    if (NULL == pfb)
    {
        perror("open_framebuffer mmap error");
        
        return -1;
    }
    // printf("pfb = %p.\n", pfb);

    return framebuffer_fd;
}

/******************************************************************************
函数名称: close_framebuffer
函数功能: 关闭framebuffer
函数参数: fd: framebuffer文件描述符
函数返回: 成功: 0
          失败: 返回-1
******************************************************************************/
int close_framebuffer(int fd)
{
    return close(fd);
}

/******************************************************************************
函数名称: framebuffer_draw_point
函数功能: framebuffer画点
函数参数: x: x轴坐标
          y: y轴坐标
          color: 需要绘制的颜色
函数返回: 无
******************************************************************************/
void framebuffer_draw_point(const unsigned int x, const unsigned int y, const unsigned int color)
{
    // (y * SCREEN_WIDTH + x): 表示像素点编号, 屏幕左上角为起始位置, 屏幕右下角为结束位置
    *(pfb + (y * SCREEN_WIDTH + x)) = color;
}

/******************************************************************************
函数名称: framebuffer_draw_line
函数功能: framebuffer画线
函数参数: start_x: 起始点x坐标
          start_y: 起始点y坐标
          end_x: 结束点x坐标
          end_y: 结束点y坐标
          color: 需要绘制的颜色
函数返回: 无
******************************************************************************/
void framebuffer_draw_line(const int start_x, const int start_y, \
                           const int end_x, const int end_y, \
                           const unsigned int color)
{
    // 已知两点坐标, 求直线方程的公式为: a*x + b*y + c = 0. 
    // 其中a, b, c的值见下面
    int x = 0;
    int y = 0;

    int a = end_y - start_y;
    int b = start_x - end_x;
    int c = end_x * start_y - start_x * end_y;

    if (abs(end_x - start_x) > abs(end_y - start_y))        // 横向划线
    {
        if (start_x < end_x)
        {
            for (x=start_x; x<=end_x; x++)
            {
                // y = ((end_y - start_y) * x + (end_x * start_y - start_x * end_y)) / (end_x - start_x);

                y = (a * x + c) / (0 - b);

                // printf("x1 = %d\r\n", x);
                // printf("y1 = %d\r\n", y);
                
                framebuffer_draw_point(x, y, color);
            }
        }
        else if (start_x > end_x)
        {
            for (x=start_x; x>=end_x; x--)
            {
                // y = ((end_y - start_y) * x + (start_x * end_y - -end_x * start_y)) / (start_x - end_x);

                y = (a * x + c) / (0 - b);

                // printf("x2 = %d\r\n", x);
                // printf("y2 = %d\r\n", y);
                
                framebuffer_draw_point(x, y, color);
            }
        }
        else
        {
            framebuffer_draw_area(start_x, end_x, start_y, end_y, color);
        }
    }
    else    // 竖向划线
    {
        if (start_y < end_y)
        {
            for (y=start_y; y<=end_y; y++)
            {
                // x = ((end_x - start_x) * y + (start_x * end_y - end_x * start_y)) / (end_y - start_y);

                x = (b * y + c) / (0 - a);

                // printf("x3 = %d\r\n", x);
                // printf("y3 = %d\r\n", y);
                
                framebuffer_draw_point(x, y, color);
            }
        }
        else if (start_y > end_y)
        {
            for (y=start_y; y>=end_y; y--)
            {
                // int a = end_y - start_y;
                // int b = start_x - end_x;
                // int c = end_x * start_y - start_x * end_y;
                // a*x + b*y + c = 0

                x = (b * y + c) / (0 - a);

                // printf("x4 = %d\r\n", x);
                // printf("y4 = %d\r\n", y);
                
                framebuffer_draw_point(x, y, color);
            }
        }
        else
        {
            framebuffer_draw_area(start_x, end_x, start_y, end_y, color);
        }
    }
    

    // for (x=(start_x < end_x ? start_x : end_x); x<=(start_x < end_x ? end_x : start_x); x++ )
    // {
    //         y = (end_y - start_y) * (x - start_x) / (end_x - start_x) + start_y;

    //         printf("x = %d\r\n", x);
    //         printf("y = %d\r\n", y);
            
    //         framebuffer_draw_point(x, y, color);
    // }
}

/******************************************************************************
函数名称: framebuffer_draw_wide_line
函数功能: framebuffer画宽线
函数参数: start_x: 起始点x坐标
          start_y: 起始点y坐标
          end_x: 结束点x坐标
          end_y: 结束点y坐标
          color: 需要绘制的颜色
          wide: 指定的线宽
函数返回: 无
******************************************************************************/
void framebuffer_draw_wide_line(const int start_x, const int start_y, \
                           const int end_x, const int end_y, \
                           const unsigned int color, const unsigned char wide)
{
    // 已知两点坐标, 求直线方程的公式为: a*x + b*y + c = 0. 
    // 其中a, b, c的值见下面
    int x = 0;
    int y = 0;

    int a = end_y - start_y;
    int b = start_x - end_x;
    int c = end_x * start_y - start_x * end_y;

    if (abs(end_x - start_x) > abs(end_y - start_y))        // 横向划线
    {
        if (start_x < end_x)
        {
            for (x=start_x; x<=end_x; x++)
            {
                y = (a * x + c) / (0 - b);

                // framebuffer_draw_point(x, y, color);
                framebuffer_draw_area(x - wide, x + wide, y - wide, y + wide, color);
            }
        }
        else if (start_x > end_x)
        {
            for (x=start_x; x>=end_x; x--)
            {
                y = (a * x + c) / (0 - b);

                // framebuffer_draw_point(x, y, color);
                framebuffer_draw_area(x - wide, x + wide, y - wide, y + wide, color);
            }
        }
        else
        {
            framebuffer_draw_area(start_x, end_x, start_y, end_y, color);
        }
    }
    else    // 竖向划线
    {
        if (start_y < end_y)
        {
            for (y=start_y; y<=end_y; y++)
            {
                x = (b * y + c) / (0 - a);

                // framebuffer_draw_point(x, y, color);

                framebuffer_draw_area(x - wide, x + wide, y - wide, y + wide, color);
            }
        }
        else if (start_y > end_y)
        {
            for (y=start_y; y>=end_y; y--)
            {
                x = (b * y + c) / (0 - a);

                // framebuffer_draw_point(x, y, color);

                framebuffer_draw_area(x - wide, x + wide, y - wide, y + wide, color);
            }
        }
        else
        {
            framebuffer_draw_area(start_x, end_x, start_y, end_y, color);
        }
    }
}

/******************************************************************************
函数名称: framebuffer_draw_background
函数功能: 绘制屏幕背景色
函数参数: screen_width: 屏幕宽度
          screen_height: 屏幕高度
          color: 需要绘制的颜色
函数返回: 无
******************************************************************************/
void framebuffer_draw_background(const unsigned int screen_width, const unsigned int screen_height, \
                                 const unsigned int color)
{
    unsigned int x = 0;     // 屏幕宽度索引
    unsigned int y = 0;     // 屏幕高度索引

    for (y=0; y<screen_height; y++)
    {
        for (x=0; x<screen_width; x++)
        {
            framebuffer_draw_point(x, y, color);
        }
    }
}

/******************************************************************************
函数名称: framebuffer_draw_area
函数功能: 屏幕指定区域绘制颜色(包含指定边界)
函数参数: x_start: x方向起始位置
          x_end: x方向结束位置
          y_start: y方向起始位置
          y_end: y方向结束位置
          color: 需要绘制的颜色
函数返回: 无
******************************************************************************/
void framebuffer_draw_area(const unsigned int x_start, const unsigned int x_end, \
                           const unsigned int y_start, const unsigned int y_end, \
                           const unsigned int color)
{
    unsigned int x = 0;     // 屏幕宽度索引
    unsigned int y = 0;     // 屏幕高度索引

    for (y=y_start; y<=y_end; y++)
    {
        for (x=x_start; x<=x_end; x++)
        {
            // framebuffer_draw_point(x, y, color);
            *(pfb + (y * SCREEN_WIDTH + x)) = color;
        }
    }
}

/******************************************************************************
函数名称: framebuffer_draw_circle
函数功能: framebuffer画圆(实心圆)
函数参数: x: 圆心坐标x
          y: 圆心坐标y
          radius: 半径
          color: 需要绘制的颜色
函数返回: 无
******************************************************************************/
void framebuffer_draw_circle(const unsigned int center_x, const unsigned int center_y, \
                             const unsigned int radius, const unsigned int color)
{
    unsigned int x_start = 0;   // 需要绘制的行的x起始位置
    unsigned int x_end = 0;     // 需要绘制的行的x结束位置
    unsigned int y_start = 0;   // 需要绘制的列的y起始位置
    unsigned int y_end = 0;     // 需要绘制的列的y结束位置
    unsigned int x_min = 0;     // 需要绘制的圆的左边界
    unsigned int x_max = 0;     // 需要绘制的圆的右边界
    unsigned int y_min = 0;     // 需要绘制的圆的上边界
    unsigned int y_max = 0;     // 需要绘制的圆的下边界
    unsigned int length = 0;      // 求得的坐标x/y偏离圆中轴线的距离

    // 计算需要绘制的圆的边界
    y_min = center_y - radius;
    y_max = center_y + radius;
    x_min = center_x - radius;
    x_max = center_x + radius;

    for (x_start = x_min; x_start < x_max; x_start++)
    {
        // 根据圆心坐标, 半径, 坐标x; 求坐标y距离
        length = sqrt(radius * radius - (x_start - center_x) * (x_start - center_x));

        // 根据距离计算y轴上下两点的y坐标值
        y_start = center_y - length;
        y_end = center_y + length;

        framebuffer_draw_area(x_start, x_start, y_start, y_end, color);
    }
    
    for (y_start = y_min; y_start < y_max; y_start++)
    {
        // 根据圆心坐标, 半径, 坐标y; 求坐标x距离
        length = sqrt(radius * radius - (y_start - center_y) * (y_start - center_y));

        // 根据距离计算x轴左右两点的x坐标值
        x_start = center_x - length;
        x_end = center_x + length;

        framebuffer_draw_area(x_start, x_end, y_start, y_start, color);
    }
}

/******************************************************************************
函数名称: framebuffer_display_picture
函数功能: 屏幕指定位置显示图片
函数参数: x_start: 图片起点坐标x值
          y_start: 图片起点坐标y值
          picture_type: 需要显示的图片类型(0: bmp, 1: jpg, 2: png)
          picture: 需要显示的图片信息
函数返回: 无
******************************************************************************/
void framebuffer_display_picture(const unsigned int x_start, const unsigned int y_start, \
                                 const char picture_type, const struct picture_info *picture)
{
    unsigned int x = 0;     // 屏幕宽度索引
    unsigned int y = 0;     // 屏幕高度索引
    unsigned int display_data_position = 0;     // 当前像素点需要显示的数据在图像数组中的位置
    unsigned int color = 0;     // 需要显示的颜色

	// printf("picture resolution: %d * %d, bpp=%d.\n", picture->width, picture->height, picture->bpp);

	if ((picture->bpp != 32) && (picture->bpp != 24))
	{
		printf("bpp %d is not support.\n", picture->bpp);

		return;
	}

    if (0 == picture_type)      // 如果是bmp格式的图片
    {
        display_data_position = picture->height * picture->width * 3 - 3;     // 第一个像素点的数据在图像数组最后

        for (y=y_start; y<(y_start + picture->height); y++)      // 这里不能使用<=进行判断, 否则图片会错位
        {
            for (x=x_start; x<(x_start + picture->width); x++)
            {
                color = (((picture->data)[display_data_position + 0] << 0) | \
                         ((picture->data)[display_data_position + 1] << 8) | \
                         ((picture->data)[display_data_position + 2] << 16));

                framebuffer_draw_point(x, y, color);
                
                display_data_position -= 3;     // 移动到下一个像素点的位置
            }
        }
    }
	else if ((1 == picture_type) || (2 == picture_type))        // 如果是jpg或者png格式的图片
    {
        display_data_position = 0;     // 第一个像素点的数据在图像数组开始

        for (y=y_start; y<(y_start + picture->height); y++)      // 这里不能使用<=进行判断, 否则图片会错位
        {
            for (x=x_start; x<(x_start + picture->width); x++)
            {
                color = (((picture->data)[display_data_position + 2] << 0) | \
                         ((picture->data)[display_data_position + 1] << 8) | \
                         ((picture->data)[display_data_position + 0] << 16));

                framebuffer_draw_point(x, y, color);

                display_data_position += 3;     // 移动到下一个像素点的位置
            }
        }
    }
    else
    {
        printf("framebuffer_display_picture unsupported picture type\r\n");

        return;
    }
}
